﻿using System;
using System.IO;
using System.Collections;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

namespace SQLServerFramework {
    public partial class NTFS {
        #region path formatting

        private static string cs_NormalizePath(string path) {
            if (path.Length == 2 && path.ToCharArray()[1] == Path.VolumeSeparatorChar) {
                path += Path.DirectorySeparatorChar;
            } else if (path.Length > 3) {
                path = path.TrimEnd(Path.DirectorySeparatorChar);
            }
            return Path.GetFullPath(path);
        }

        private static string cs_BuildPath(string parentPath, string item) {
            return cs_NormalizePath(string.Format("{0}{1}{2}", cs_NormalizePath(parentPath), Path.DirectorySeparatorChar, item.Trim(Path.DirectorySeparatorChar)));
        }

        [return: SqlFacet(MaxSize = 512)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "fs_ntfs_normalize_path"
        )]
        public static SqlString NormalizePath(
            [SqlFacet(MaxSize = 512)]SqlString path) {
            return cs_NormalizePath(path.Value);
        }

        [return: SqlFacet(MaxSize = 512)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "fs_ntfs_build_normalized_path"
        )]
        public static SqlString BuildNormalizedPath(
            [SqlFacet(MaxSize = 512)]SqlString parentPath
            , [SqlFacet(MaxSize = 512)]SqlString item) {
            return cs_BuildPath(parentPath.Value, item.Value);
        }

        #endregion

        #region path segmentation

        [return: SqlFacet(MaxSize = 512)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "fs_ntfs_get_path_filename"
        )]
        public static SqlString GetPathFilename([SqlFacet(MaxSize = 512)]SqlString path) {
            return Path.GetFileName(cs_NormalizePath(path.Value));
        }

        [return: SqlFacet(MaxSize = 512)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "fs_ntfs_get_path_parent"
        )]
        public static SqlString GetPathParent([SqlFacet(MaxSize = 512)]SqlString path) {
            return Directory.GetParent(path.Value).FullName;
        }

        #endregion

        #region file system browsing

        private static void Dir_FillRow(object obj
            //1/ when browsing "C:\data\input", filling record for "C:\data\input\download\ftp\file.txt"
            //2/ when browsing "\\servername\share\data\input", filling record for "\\servername\share\data\input\download\ftp\file.txt"
                , out bool isDirectory
            //1/  false
            //2/  false
                , out string name
            //1/  file.txt
            //2/  file.txt
                , out string path
            //1/  C:\data\input\download\ftp\file.txt
            //2/  \\servername\share\data\input\download\ftp\file.txt
                , out string relativePath
            //1/  download\ftp\file.txt
            //2/  download\ftp\file.txt
                , out string parentName
            //1/  ftp
            //2/  ftp
                , out string parentPath
            //1/  C:\data\input\ftp
            //2/  \\servername\share\data\input\ftp
                , out string relativeParentPath
            //1/  download\ftp
            //2/  download\ftp
                , out string rootName
            //1/  C:\   
            //2/  share  
                , out string rootPath
            //1/  C:\
            //2/  \\servername\share
                , out long size
                , out string extension
                , out bool isReadOnly
                , out DateTime creationTime
                , out DateTime lastAccessTime
                , out DateTime lastWriteTime) {
            object[] row = (object[])obj;

            isDirectory = (bool)row[0];
            name = (string)row[1];
            path = (string)row[2];
            relativePath = (string)row[3];
            parentName = (string)row[4];
            parentPath = (string)row[5];
            relativeParentPath = (string)row[6];
            rootName = (string)row[7];
            rootPath = (string)row[8];
            size = (long)row[9];
            extension = (string)row[10];
            isReadOnly = (bool)row[11];
            creationTime = (DateTime)row[12];
            lastAccessTime = (DateTime)row[13];
            lastWriteTime = (DateTime)row[14];
        }

        [SqlFunction(
            DataAccess = DataAccessKind.None
            , FillRowMethodName = "Dir_FillRow"
            , IsDeterministic = false
            , IsPrecise = true
            , Name = "fs_ntfs_dir"
            , SystemDataAccess = SystemDataAccessKind.Read
            , TableDefinition =
    @"directory_fl BIT
, name NVARCHAR(4000)
, path NVARCHAR(4000)
, relative_path NVARCHAR(4000)
, parent_name NVARCHAR(4000)
, parent_path NVARCHAR(4000)
, relative_parent_path NVARCHAR(4000)
, root_name NVARCHAR(4000)
, root_path NVARCHAR(4000)
, size BIGINT
, extension NVARCHAR(4000)
, read_only_fl BIT
, creation_time DATETIME
, last_access_time DATETIME
, last_write_time DATETIME")]
        public static IEnumerable Dir([SqlFacet(MaxSize = 4000)]SqlString path, [SqlFacet(MaxSize = 4000)]SqlString searchPattern, SqlBoolean recursive) {
            return cs_Dir(path.IsNull ? null : path.Value, searchPattern.IsNull ? null : searchPattern.Value, recursive.IsNull ? false : recursive.Value);
        }

        private static IEnumerable cs_Dir(string path, string searchPattern, bool recursive) {
            if (path == null) { throw new ArgumentNullException(@"path"); }
            DirectoryInfo di = new DirectoryInfo(cs_NormalizePath(path));
            if (!di.Exists) { throw new DirectoryNotFoundException(path); }

            ArrayList rows = new ArrayList();
            searchPattern = searchPattern == null ? @"*" : searchPattern;

            int diFNL = di.FullName.Length;

            foreach (FileInfo file in di.GetFiles(searchPattern, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) {
                rows.Add(new object[] { 
				false
                , file.Name
				, file.FullName
				, file.FullName.Substring(diFNL).Trim(Path.DirectorySeparatorChar)
				, file.Directory.Name
				, file.Directory.FullName
				, file.Directory.FullName.Substring(diFNL).Trim(Path.DirectorySeparatorChar)
				, di.Root.Name
				, di.Root.FullName
				, file.Length
				, file.Extension
				, file.IsReadOnly
				, file.CreationTime
				, file.LastAccessTime
				, file.LastWriteTime });
            }
            foreach (DirectoryInfo sdi in di.GetDirectories(searchPattern, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) {
                rows.Add(new object[] {
				true
                , sdi.Name
				, sdi.FullName
				, sdi.FullName.Substring(diFNL).Trim(Path.DirectorySeparatorChar)
				, sdi.Parent.Name
				, sdi.Parent.FullName
				, sdi.Parent.FullName.Substring(diFNL).Trim(Path.DirectorySeparatorChar)
				, sdi.Root.Name
				, sdi.Root.FullName
                , 0L  //size
                , null  //extension
                , false  //read-only
				, sdi.CreationTime
				, sdi.LastAccessTime
				, sdi.LastWriteTime});
            }

            return rows;
        }

        #endregion
    }
}